/**
 * @typedef CState
 * @property {number} callstate
 * @property {string} statetime
 * @property {number} hostmillis
 * 
 * @typedef GCallEvent
 * @property {number} offts
 * @property {number} callstate3
 * @property {string} anstime3
 * @property {string} anstime
 * @property {number} hostmillis
 * @property {number} callstate
 * @property {'0'|'1'} direct
 * @property {} caller
 * @property {} called
 * @property {} callid
 * @property {} usid
 * @property {} queueid
 * @property {} recfile
 * @property {} dataccid
 * 
 * @typedef SendActions
 * @property {} checkin
 * @property {} checkin2
 * @property {} online
 * @property {} offline
 * @property {} checkout
 * @property {{agentids: any}} obtainaenglist 查询座席列表(只查询在线座席)
 * @property {{queid: any}} obtainqueinfo 查询队列状态
 * @property {{telno: any, exinfo: any}} calluser
 * @property {} dropcall 挂机
 * @property {{telno: any, exinfo: any}} makecall
 * @property {{telno: any, exinfo: any}} consult
 * @property {} freeseat 结束内线咨询
 * @property {} droptouser 结束外线咨询
 * @property {{type: 0|1|2}} setSeatState 0 示闲，1 示忙，2 小休
 * @property {} holduser
 * @property {} unholduser
 * @property {} transseat 转坐席
 * @property {} transcall 转坐席
 * @property {{flowtype: any, rootactid: any, actid: any, extinfo: any}} gotoivr
 * @property {} inmeet 加入会议
 * @property {} outmeet 退出会议
 * @property {{toseatno: any}} callseat 咨询座席
 * @property {{toseatno: any, extinfo: any}} callseatEx
 * @property {{queueid: any, extinfo: any}} transqueue
 * @property {{toseatno: any}} monitor 发起监听， `toseatno` 被监听坐席工号
 * @property {{toseatno: any}} outmoniter 结束监听
 * @property {} insert 插话=会议
 * @property {} outinsert 退出会议
 * @property {} whisper 耳语=指导坐席
 * @property {} outwhisper
 * @property {} robcall
 * @property {} stopcall
 * @property {{toseatno: any}} tocheckout
 * @property {{toseatno: any, type: any}} tosetbusy
 * @property {{worknos: any}} getAgcListState
 * @property {{queueid: any}} getEntQueueList
 * 
 * @typedef CallEvent
 * @property {[calltype: string, telno: string, callid: string, queid: string, uudata: string]} OnAuthSuccess 鉴权成功事件
 * @property {[calltype: string, telno: string, callid: string, queid: string, uudata: string]} OnCalling 振铃事件
 * @property {[calltype: string, telno: string, callid: string, queid: string, recfile: string, uudata: string]} OnCallConnect 接通事件
 * @property {[calltype: string]} OnCallHangup 挂机事件
 * @property {[]} changeSeatState 改变坐席状态
 * @property {[]} changeIVR 改变IVR
 * @property {[]} changeConsult
 * @property {[]} socketConnected 套接字已连接
 * @property {[telno: string, exinfo: string]} OnCustomCall 自定义呼叫
 * @property {[errcode: string]} OnCallReturn
 */

/**
 * 数组是否包含指定元素
 * @param {any[]} arr
 */
const oneOf = (arr, value) => arr.includes(value)

const pad0 = v => v > 9 ? v : '0' + v

/**
 * 计算时差
 * @param {Date} beginTime 
 */
const calcTimeSpan = beginTime => {
  const date = new Date(2020, 1, 1)
  date.setMilliseconds(Math.max(new Date() - beginTime, 0))
  return `${pad0(date.getHours())}:${pad0(date.getMinutes())}:${pad0(date.getSeconds())}`
}

/**
 * 日志输出
 * @param {string} msg
 * @param {'info'|'debug'|'error'} logType
 */
const timeLog = (msg, logType) => {
  const contentCss = 'color:initial;'
  const date = new Date().toLocaleTimeString()
  if (logType === 'info') {
    console.log(`%c${date} Info: %c${msg}`, 'color:#00cdb1;', contentCss)
  } else if (logType === 'error') {
    console.log(`%c${date} Error: %c${msg}`, 'color:red;', contentCss)
  } else {
    console.log(`%c${date} Debug: %c ${msg}`, 'color:yellow;', contentCss)
  }
}

/**
 * @template {keyof CallEvent} T
 * @param {T} eventType
 * @param  {CallEvent[T]} args
 */
const sendEventToParentWindow = (eventType, ...args) => {
  window.postMessage(
    JSON.stringify({ type: eventType, payload: args })
  )
}

const callStateHelper = {
  /** 呼叫状态 */ callState: 1,
  /** 状态更新定时器ID */ tid: null,

  /** 未连接 */ DISCONNECT: -1,
  /** 签出 */ LOGOUT: 0,
  /** 空闲 */ LOGIN: 1,
  /** 上线 */ ONLINE: 2,
  /** 通话 */ TALKING: 3,
  /** 拨号中 */ USING: 30,
  /** 呼叫用户 */ CALL_CUSTING: 31,
  /** 用户振铃 */ CUST_RING: 32,
  /** 忙碌 */ BUSY: 4,
  /** 保持 */ HOLD: 5,
  /** 咨询坐席中 */ CONSULT_AGC: 6,
  /** 咨询外部用户（专家席） */ CONSULT_USER: 7,
  /** 会议 */ CONFERENCE: 8,
  /** 监听 */ MONITOR: 9,
  /** 插话 */ INSERT: 10,
  /** 上线忙 */ BUSY_ONLINE: 11,
  /** 振铃 */ RING: 12,
  /** 耳语 */ WHISPER: 13,
  /** 同事 */ AGC_TALK: 14,
  /** 咨询呼出 */ RING_3: 15,
  /** 签入忙 */ BUSY_LOGIN: 209,
  /** 签入忙 */ BUSY_ONLINE_LOGIN: 219,
  /** 上线忙 - 小休 */ BUSY_ONLINE_201: 201,
  /** 小休 */ BUSY_REST: 202,
  /** 忙碌 */ BUSY_211: 211,
  /** 小休 */ BUSY_ONLINE_REST: 212,
  /** 拦截 */ ROB_CALL: 220,

  getText(state) {
    state = +state
    const _self = this

    if (oneOf([_self.BUSY, _self.BUSY_ONLINE_201, _self.BUSY_211], state)) {
      state = _self.BUSY
    } else if (oneOf([_self.CONSULT_AGC, _self.CONSULT_USER], state)) {
      state = _self.CONSULT_AGC
    } else if (oneOf([_self.BUSY_REST, _self.BUSY_ONLINE_REST], state)) {
      state = _self.BUSY_REST
    } else if (oneOf([_self.BUSY_LOGIN, _self.BUSY_ONLINE_LOGIN], state)) {
      state = _self.BUSY_LOGIN
    }

    return ({
      [_self.DISCONNECT]: '未连接',
      [_self.LOGOUT]: '签出',
      [_self.LOGIN]: '空闲',
      [_self.ONLINE]: '上线',
      [_self.USING]: '拨号中',
      [_self.TALKING]: '通话',
      [_self.CALL_CUSTING]: '呼叫用户',
      [_self.CUST_RING]: '用户振铃',
      [_self.BUSY]: '忙碌',
      [_self.CONSULT_AGC]: '咨询',
      [_self.CONFERENCE]: '会议',
      [_self.MONITOR]: '监听',
      [_self.INSERT]: '插话',
      [_self.BUSY_ONLINE]: '上线忙',
      [_self.RING]: '振铃',
      [_self.WHISPER]: '耳语',
      [_self.AGC_TALK]: '同事',
      [_self.RING_3]: '咨询呼出',
      [_self.BUSY_REST]: '小休',
      [_self.BUSY_LOGIN]: '签入忙',
      [_self.ROB_CALL]: '拦截'
    })[state] || `未知${state}`
  },

  /**
   * 更新状态文本
   * @param {string} msg 
   */
  updateShowText(msg) {
    const el = document.getElementById('hs_mainstatus')
    if (el) {
      el.innerHTML = msg
    }
  },

  /**
   * @param {string} msg
   * @param {Date} beginTime
   */
  startTimer(msg, beginTime) {
    this.stopTimer()
    const _self = this
      ; (function loopFunc() {
        callStateHelper.updateShowText(`${msg} ${calcTimeSpan(beginTime)}`)
        _self.tid = setTimeout(loopFunc, 1000)
      })()
  },

  stopTimer() {
    clearTimeout(this.tid)
    this.tid = null
  },

  /**
   * @param {CState} state
   */
  updateStateShow(state) {
    const _self = this
    const text = _self.getText(state.callstate)
    const offts = (Date.now() - state.hostmillis) || 0
    const tdt = new Date(state.statetime)
    const rDt = new Date(+tdt + offts)

    _self.stopTimer()

    switch (+state.callstate) {
      case _self.LOGOUT:
      case _self.DISCONNECT:
        return _self.updateShowText(text)
      case _self.ONLINE:
      case _self.LOGIN:
      case _self.BUSY:
      case _self.BUSY_ONLINE:
      case _self.BUSY_LOGIN:
      case _self.BUSY_ONLINE_LOGIN:
      case _self.BUSY_ONLINE_201:
      case _self.BUSY_REST:
      case _self.BUSY_211:
      case _self.USING:
      case _self.BUSY_ONLINE_REST:
        return _self.startTimer(text, rDt)
      case _self.HOLD:
      case _self.RING:
      case _self.MONITOR:
      case _self.INSERT:
      case _self.ROB_CALL:
      case _self.AGC_TALK:
      case _self.CALL_CUSTING:
      case _self.CUST_RING:
        return _self.startTimer(`${text}:`, rDt)
      case _self.CONSULT_AGC:
      case _self.CONSULT_USER:
      case _self.CONFERENCE:
      case _self.TALKING:
        let offts = 0
        let tdt = new Date()
        const FA = FlashAgentX
        if (FA.g_callEvent) {
          offts = FA.g_callEvent.offts
          if (+FA.g_callEvent.callstate3 === 2 && FA.g_callEvent.anstime3) {
            tdt = new Date(FA.g_callEvent.anstime3)
          } else {
            if (FA.g_callEvent.anstime) {
              tdt = new Date(FA.g_callEvent.anstime)
            }
          }
        }
        return _self.startTimer(`${text}:`, new Date(+tdt + offts))
    }
  },

  /**
   * @param {CState} state
   */
  updateBtnsStyle(state) {
    const _self = this
    _self.callState = state.callstate
    switch (state.callstate) {
      case _self.DISCONNECT:
        setBtnStyle('btn_onoff_line', 0, 1)
        setBtnStyle('btn_ready_busy', 0, 1)
        setBtnStyle('btn_callout', 0, 1)
        setBtnStyle('btn_hold_unhold', 0, 1)
        setBtnStyle('btn_login', 0, 0)
        setBtnStyle('btn_connect', 0, 0) // 随时可以连接其他 通信节点，因此全开	
        setBtnStyle('btn_consult', 0, 1)
        setBtnStyle('btn_transfer', 0, 1) // 咨询中允许转移咨询
        setBtnStyle('btn_confence', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_toivr', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_dtmf', 0, 1)
        setBtnStyle('btn_answer', 0, 1)
        setBtnStyle('btn_reject', 0, 1)
        setBtnStyle('btn_monitor', 0, 1)
        setBtnStyle('btn_stop_monitor', 0, 1)
        setBtnStyle('btn_force_join', 0, 1)
        setBtnStyle('btn_whisper', 0, 1)
        setBtnStyle('btn_tokeover', 0, 1)
        setBtnStyle('btn_force_hungup', 0, 1)
        setBtnStyle('btn_monqueue', 0, 1)
        break
      case _self.LOGOUT:
        setBtnStyle('btn_onoff_line', 0, 1)
        setBtnStyle('btn_ready_busy', 0, 1)
        setBtnStyle('btn_callout', 0, 1)
        setBtnStyle('btn_hold_unhold', 0, 1)
        setBtnStyle('btn_login', 0, 0)
        setBtnStyle('btn_connect', 0, 1)  // 随时可以连接其他 通信节点，因此全开
        setBtnStyle('btn_consult', 0, 1)  // 咨询中允许停止咨询
        setBtnStyle('btn_transfer', 0, 1)  // 咨询中允许转移咨询
        setBtnStyle('btn_confence', 0, 1)  // 咨询中可以三方
        setBtnStyle('btn_ivr_auth', 0, 1)  // 咨询中允许转移咨询
        setBtnStyle('btn_toivr', 0, 1)  // 咨询中可以三方
        setBtnStyle('btn_connect', 0, 1)
        setBtnStyle('btn_monitor', 0, 1)
        setBtnStyle('btn_stop_monitor', 0, 1)
        setBtnStyle('btn_force_join', 0, 1)
        setBtnStyle('btn_whisper', 0, 1)
        setBtnStyle('btn_tokeover', 0, 1)
        setBtnStyle('btn_force_hungup', 0, 1)
        setBtnStyle('btn_monqueue', 0, 1)
        break
      case _self.LOGIN:
        setBtnStyle('btn_onoff_line', 0, 0)
        setBtnStyle('btn_ready_busy', 0, 0)
        setBtnStyle('btn_callout', 0, 0)
        setBtnStyle('btn_hold_unhold', 0, 1)
        setBtnStyle('btn_login', 1, 0)
        setBtnStyle('btn_connect', 0, 1) // 随时可以连接其他 通信节点，因此全开				
        setBtnStyle('btn_consult', 0, 1) // 无通话的咨询 =坐席间通话
        setBtnStyle('btn_transfer', 0, 1) // 咨询中允许转移咨询
        setBtnStyle('btn_confence', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_toivr', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_monitor', 0, 0)
        setBtnStyle('btn_stop_monitor', 0, 1)
        setBtnStyle('btn_force_join', 0, 1)
        setBtnStyle('btn_whisper', 0, 1)
        setBtnStyle('btn_tokeover', 0, 1)
        setBtnStyle('btn_force_hungup', 0, 1)
        setBtnStyle('btn_monqueue', 0, 0)
        break
      case _self.ONLINE:
        setBtnStyle('btn_onoff_line', 1, 0)
        setBtnStyle('btn_ready_busy', 0, 0)
        setBtnStyle('btn_callout', 0, 0)
        setBtnStyle('btn_hold_unhold', 0, 1)
        setBtnStyle('btn_login', 1, 0)
        setBtnStyle('btn_connect', 0, 1) //随时可以连接其他 通信节点，因此全开
        setBtnStyle('btn_consult', 0, 1) //咨询中允许停止咨询
        setBtnStyle('btn_transfer', 0, 1) //咨询中允许转移咨询
        setBtnStyle('btn_confence', 0, 1) //咨询中可以三方
        setBtnStyle('btn_toivr', 0, 1) //咨询中可以三方
        setBtnStyle('btn_monitor', 0, 0)
        setBtnStyle('btn_stop_monitor', 0, 1)
        setBtnStyle('btn_force_join', 0, 1)
        setBtnStyle('btn_whisper', 0, 1)
        setBtnStyle('btn_tokeover', 0, 1)
        setBtnStyle('btn_force_hungup', 0, 1)
        setBtnStyle('btn_monqueue', 0, 0)
        break
      case _self.USING:
        setBtnEnable('btn_onoff_line', 1)
        setBtnEnable('btn_ready_busy', 1)
        setBtnStyle('btn_callout', 1, 0)
        setBtnStyle('btn_hold_unhold', 0, 1)
        setBtnStyle('btn_login', 1, 1)
        setBtnStyle('btn_connect', 0, 1)
        setBtnStyle('btn_consult', 0, 1) // 咨询中允许停止咨询
        setBtnStyle('btn_transfer', 0, 1) // 咨询中允许转移咨询
        setBtnStyle('btn_confence', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_toivr', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_monitor', 0, 1)
        setBtnStyle('btn_stop_monitor', 0, 1)
        setBtnStyle('btn_force_join', 0, 1)
        setBtnStyle('btn_whisper', 0, 1)
        setBtnStyle('btn_tokeover', 0, 1)
        setBtnStyle('btn_force_hungup', 0, 1)
        setBtnStyle('btn_monqueue', 0, 0)
        break
      case _self.TALKING:
        setBtnEnable('btn_onoff_line', 1)
        setBtnEnable('btn_ready_busy', 1)
        setBtnStyle('btn_callout', 1, 0)
        setBtnStyle('btn_hold_unhold', 0, 0)
        setBtnStyle('btn_login', 1, 1)
        setBtnStyle('btn_connect', 0, 1)
        setBtnStyle('btn_consult', 0, 0) // 咨询中允许停止咨询
        setBtnStyle('btn_transfer', 0, 1) // 咨询中允许转移咨询
        setBtnStyle('btn_confence', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_toivr', 0, 0) // 咨询中可以三方
        setBtnStyle('btn_monitor', 0, 1)
        setBtnStyle('btn_stop_monitor', 0, 1)
        setBtnStyle('btn_force_join', 0, 1)
        setBtnStyle('btn_whisper', 0, 1)
        setBtnStyle('btn_tokeover', 0, 1)
        setBtnStyle('btn_force_hungup', 0, 1)
        setBtnStyle('btn_monqueue', 0, 0)
        break
      case _self.BUSY:
      case _self.BUSY_LOGIN:
      case _self.BUSY_REST:
      case _self.BUSY_ONLINE_201:
        setBtnStyle('btn_onoff_line', 0, 0)
        setBtnStyle('btn_ready_busy', 1, 0)
        setBtnStyle('btn_callout', 0, 0)
        setBtnStyle('btn_hold_unhold', 0, 1)
        setBtnStyle('btn_login', 1, 0)
        setBtnStyle('btn_connect', 0, 1) // 随时可以连接其他 通信节点，因此全开			
        setBtnStyle('btn_consult', 0, 1) // 咨询中允许停止咨询
        setBtnStyle('btn_transfer', 0, 1) // 咨询中允许转移咨询
        setBtnStyle('btn_confence', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_toivr', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_monitor', 0, 1)
        setBtnStyle('btn_stop_monitor', 0, 1)
        setBtnStyle('btn_force_join', 0, 1)
        setBtnStyle('btn_whisper', 0, 1)
        setBtnStyle('btn_tokeover', 0, 1)
        setBtnStyle('btn_force_hungup', 0, 1)
        setBtnStyle('btn_monqueue', 0, 0)
        break
      case _self.HOLD:
        setBtnEnable('btn_onoff_line', 1)
        setBtnEnable('btn_ready_busy', 1)
        setBtnStyle('btn_callout', 1, 0) // hold客户
        setBtnStyle('btn_hold_unhold', 1, 0)
        setBtnStyle('btn_login', 1, 1)
        setBtnStyle('btn_connect', 0, 1) // 随时可以连接其他 通信节点，因此全开			
        setBtnStyle('btn_consult', 0, 0) // 咨询中允许停止咨询
        setBtnStyle('btn_transfer', 0, 1) // 咨询中允许转移咨询
        setBtnStyle('btn_confence', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_toivr', 0, 0) // 咨询中可以三方
        setBtnStyle('btn_monitor', 0, 1)
        setBtnStyle('btn_stop_monitor', 0, 1)
        setBtnStyle('btn_force_join', 0, 1)
        setBtnStyle('btn_whisper', 0, 1)
        setBtnStyle('btn_tokeover', 0, 1)
        setBtnStyle('btn_force_hungup', 0, 1)
        setBtnStyle('btn_monqueue', 0, 0)
        break
      case _self.CONSULT_AGC:
        setBtnStyle('btn_hold_unhold', 1, 1)
        setBtnStyle('btn_consult', 1, 0) // 咨询中允许停止咨询
        setBtnStyle('btn_transfer', 0, 0) // 咨询中允许转移咨询
        setBtnStyle('btn_confence', 0, 0) // 0=内三方，1=开始外三，2=结束内三方，3=结束外三	 
        setBtnStyle('btn_callout', 1, 1) // hold客户		
        setBtnStyle('btn_toivr', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_monitor', 0, 1)
        setBtnStyle('btn_stop_monitor', 0, 1)
        setBtnStyle('btn_force_join', 0, 1)
        setBtnStyle('btn_whisper', 0, 1)
        setBtnStyle('btn_tokeover', 0, 1)
        setBtnStyle('btn_force_hungup', 0, 1)
        setBtnStyle('btn_monqueue', 0, 0)
        break
      case _self.CONSULT_USER:
        setBtnStyle('btn_hold_unhold', 1, 1)
        setBtnStyle('btn_callout', 1, 1) // hold客户
        setBtnStyle('btn_consult', 2, 0) // 可以退出外线咨询
        setBtnStyle('btn_transfer', 1, 0) // 转外线咨询席
        setBtnStyle('btn_confence', 1, 0) // 咨询中可以三方 0=内三方，1=开始外三，2=结束内三方，3=结束外三			
        setBtnStyle('btn_toivr', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_monitor', 0, 1)
        setBtnStyle('btn_stop_monitor', 0, 1)
        setBtnStyle('btn_force_join', 0, 1)
        setBtnStyle('btn_whisper', 0, 1)
        setBtnStyle('btn_tokeover', 0, 1)
        setBtnStyle('btn_force_hungup', 0, 1)
        setBtnStyle('btn_monqueue', 0, 0)
        break
      case _self.RING_3:
        setBtnStyle('btn_hold_unhold', 1, 1)
        setBtnStyle('btn_callout', 1, 1) // hold客户
        setBtnStyle('btn_consult', 2, 0) // 可以退出外线咨询
        setBtnStyle('btn_transfer', 1, 1) // 转外线咨询席
        setBtnStyle('btn_confence', 1, 1) // 咨询中可以三方 0=内三方，1=开始外三，2=结束内三方，3=结束外三			
        setBtnStyle('btn_toivr', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_monitor', 0, 1)
        setBtnStyle('btn_stop_monitor', 0, 1)
        setBtnStyle('btn_force_join', 0, 1)
        setBtnStyle('btn_whisper', 0, 1)
        setBtnStyle('btn_tokeover', 0, 1)
        setBtnStyle('btn_force_hungup', 0, 1)
        setBtnStyle('btn_monqueue', 0, 0)
        break
      case _self.CONFERENCE:
        setBtnStyle('btn_hold_unhold', 0, 1)
        setBtnStyle('btn_callout', 1, 1) // hold客户
        setBtnStyle('btn_login', 1, 0)
        setBtnStyle('btn_consult', 0, 1)
        setBtnStyle('btn_transfer', 0, 1)
        setBtnStyle('btn_confence', 2, 0) // 0=内三方，1=开始外三，2=结束内三方，3=结束外三	
        setBtnStyle('btn_toivr', 0, 1) // 咨询中可以三方	
        setBtnStyle('btn_monitor', 0, 1)
        setBtnStyle('btn_stop_monitor', 0, 1)
        setBtnStyle('btn_force_join', 0, 1)
        setBtnStyle('btn_whisper', 0, 1)
        setBtnStyle('btn_tokeover', 0, 1)
        setBtnStyle('btn_force_hungup', 0, 1)
        setBtnStyle('btn_monqueue', 0, 0)
        break
      case _self.MONITOR:
        setBtnStyle('btn_onoff_line', 0, 1)
        setBtnStyle('btn_ready_busy', 0, 1)
        setBtnStyle('btn_callout', 1, 1)
        setBtnStyle('btn_hold_unhold', 0, 1)
        setBtnStyle('btn_login', 1, 0)
        setBtnStyle('btn_connect', 0, 0) // 随时可以连接其他 通信节点，因此全开
        setBtnStyle('btn_consult', 0, 1) // 咨询中允许停止咨询
        setBtnStyle('btn_transfer', 0, 1) // 咨询中允许转移咨询
        setBtnStyle('btn_confence', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_toivr', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_monitor', 0, 1) // 监听
        setBtnStyle('btn_stop_monitor', 0, 0) // 停止监听     
        setBtnStyle('btn_force_join', 0, 0) // 强插
        setBtnStyle('btn_whisper', 0, 0) // 耳语
        setBtnStyle('btn_tokeover', 0, 0) // 拦截
        setBtnStyle('btn_force_hungup', 0, 0) // 强拆
        setBtnStyle('btn_monqueue', 0, 0)
        break
      case _self.INSERT:
        setBtnStyle('btn_onoff_line', 0, 1)
        setBtnStyle('btn_callout', 1, 1)
        setBtnStyle('btn_callout', 0, 1)
        setBtnStyle('btn_hold_unhold', 0, 1)
        setBtnStyle('btn_login', 1, 0)
        setBtnStyle('btn_connect', 0, 0) // 随时可以连接其他 通信节点，因此全开
        setBtnStyle('btn_transfer', 0, 1) // 咨询中允许转移咨询
        setBtnStyle('btn_confence', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_toivr', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_monitor', 0, 1) // 监听
        setBtnStyle('btn_stop_monitor', 0, 1) // 停止监听     
        setBtnStyle('btn_force_join', 1, 0) // 强插
        setBtnStyle('btn_whisper', 0, 1) // 耳语
        setBtnStyle('btn_tokeover', 0, 1) // 拦截
        setBtnStyle('btn_force_hungup', 0, 1) // 强拆
        setBtnStyle('btn_monqueue', 0, 0)
        break
      case _self.WHISPER:
        setBtnStyle('btn_onoff_line', 0, 1)
        setBtnStyle('btn_ready_busy', 0, 1)
        setBtnStyle('btn_callout', 1, 1)
        setBtnStyle('btn_hold_unhold', 0, 1)
        setBtnStyle('btn_login', 1, 0)
        setBtnStyle('btn_connect', 0, 1) // 随时可以连接其他 通信节点，因此全开
        setBtnStyle('btn_consult', 0, 1) // 咨询中允许停止咨询
        setBtnStyle('btn_transfer', 0, 1) // 咨询中允许转移咨询
        setBtnStyle('btn_confence', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_toivr', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_monitor', 0, 1) // 监听
        setBtnStyle('btn_stop_monitor', 0, 1) // 停止监听     
        setBtnStyle('btn_force_join', 0, 1) // 强插
        setBtnStyle('btn_whisper', 1, 0) // 耳语
        setBtnStyle('btn_tokeover', 0, 1) // 拦截
        setBtnStyle('btn_force_hungup', 0, 1) // 强拆
        setBtnStyle('btn_monqueue', 0, 0)
        break
      case _self.ROB_CALL:
        setBtnStyle('btn_onoff_line', 0, 1)
        setBtnStyle('btn_ready_busy', 0, 1)
        setBtnStyle('btn_callout', 1, 1)
        setBtnStyle('btn_hold_unhold', 0, 1)
        setBtnStyle('btn_login', 1, 0)
        setBtnStyle('btn_connect', 0, 1) // 随时可以连接其他 通信节点，因此全开
        setBtnStyle('btn_consult', 0, 1) // 咨询中允许停止咨询
        setBtnStyle('btn_transfer', 0, 1) // 咨询中允许转移咨询
        setBtnStyle('btn_confence', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_toivr', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_monitor', 0, 1) // 监听
        setBtnStyle('btn_stop_monitor', 0, 1) // 停止监听     
        setBtnStyle('btn_force_join', 0, 1) // 强插
        setBtnStyle('btn_whisper', 0, 1) // 耳语
        setBtnStyle('btn_tokeover', 1, 0) // 拦截
        setBtnStyle('btn_force_hungup', 0, 1) // 强拆
        setBtnStyle('btn_monqueue', 0, 0)
        break
      case _self.BUSY_211:
      case _self.BUSY_ONLINE:
      case _self.BUSY_ONLINE_REST:
      case _self.BUSY_ONLINE_LOGIN:
        setBtnStyle('btn_onoff_line', 1, 0)
        setBtnStyle('btn_ready_busy', 1, 0)
        setBtnStyle('btn_callout', 0, 0)
        setBtnStyle('btn_hold_unhold', 0, 1)
        setBtnStyle('btn_toivr', 0, 1)
        setBtnStyle('btn_login', 1, 0)
        setBtnStyle('btn_connect', 0, 0) // 随时可以连接其他 通信节点，因此全开
        setBtnStyle('btn_consult', 0, 1) // 咨询中允许停止咨询
        setBtnStyle('btn_transfer', 0, 1) // 咨询中允许转移咨询
        setBtnStyle('btn_confence', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_toivr', 0, 1) // 咨询中可以三方
        setBtnStyle('btn_monqueue', 0, 0)
        break
      case _self.RING:
      case _self.CALL_CUSTING:
        setBtnEnable('btn_onoff_line', 1)
        setBtnEnable('btn_ready_busy', 1)
        setBtnStyle('btn_callout', 1, 0) // 通话中，可以发送 dtmf 信号
        setBtnStyle('btn_consult', 0, 1) // 咨询中允许停止咨询
        setBtnStyle('btn_hold_unhold', 0, 1)
        setBtnStyle('btn_toivr', 0, 1)
        setBtnStyle('btn_login', 1, 1)
        setBtnStyle('btn_connect', 0, 1)
        setBtnStyle('btn_monitor', 0, 1) // 监听
        setBtnStyle('btn_stop_monitor', 0, 1) // 停止监听     
        setBtnStyle('btn_force_join', 0, 1) // 强插
        setBtnStyle('btn_whisper', 0, 1) // 耳语
        setBtnStyle('btn_tokeover', 0, 1) // 拦截
        setBtnStyle('btn_force_hungup', 0, 1) // 挂断
        setBtnStyle('btn_monqueue', 0, 0)
        break
      case _self.AGC_TALK:
        setBtnEnable('btn_onoff_line', 1)
        setBtnEnable('btn_ready_busy', 1)
        setBtnStyle('btn_callout', 1, 0)
        setBtnStyle('btn_hold_unhold', 0, 1)
        setBtnStyle('btn_login', 1, 1)
        setBtnStyle('btn_connect', 0, 1)
        setBtnStyle('btn_consult', 0, 1)  // 咨询中允许停止咨询
        setBtnStyle('btn_transfer', 0, 1)  // 咨询中允许转移咨询
        setBtnStyle('btn_confence', 0, 1)  // 咨询中可以三方
        setBtnStyle('btn_toivr', 0, 1)  // 咨询中可以三方
        setBtnStyle('btn_monitor', 0, 1)  // 监听
        setBtnStyle('btn_stop_monitor', 0, 1)    // 停止监听     
        setBtnStyle('btn_force_join', 0, 1)  // 强插
        setBtnStyle('btn_whisper', 0, 1)  // 耳语
        setBtnStyle('btn_tokeover', 0, 1)  // 拦截
        setBtnStyle('btn_force_hungup', 0, 1) // 挂断
        setBtnStyle('btn_monqueue', 0, 0)
        break
    }
    _self.updateStateShow(state)
  },

  /**
   * @param {number} callstate
   */
  updateCallState(callstate) {
    const now = new Date()
    const year = now.getFullYear()
    const month = pad0(now.getMonth() + 1)
    const day = pad0(now.getDate())
    const hour = pad0(now.getHours())
    const min = pad0(now.getMinutes())
    const sec = pad0(now.getSeconds())
    const statetime = `${year}-${month}-${day} ${hour}:${min}:${sec}`
    this.updateBtnsStyle({ callstate, statetime, hostmillis: +now })
  }
}

const genUuid = () => {
  const s = []
  const hexDigits = '0123456789abcdef'
  for (let i = 0; i < 36; i++) {
    s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
  }
  s[14] = '4'
  s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1)
  s[8] = s[13] = s[18] = s[23] = '-'
  return s.join('')
}

const svcCode = {
  CHECK_IN: 1001,
  CHECK_IN_2: 1002,
  CALL_SEAT: 1003,
  CALL_TO_USER: 1004,
  CALL_USER: 1005,
  CHECK_OUT: 1006,
  DROP_TO_USER: 1007,
  DROP_USER: 1008,
  FREE_SEAT: 1009,
  GO_TO_FLOW: 1010,
  HOLDER_USER: 1011,
  INMEET: 1012,
  INSERT: 1013,
  MONITER: 1014,
  OFFLINE: 1015,
  ONLINE: 1016,
  OUT_INSERT: 1017,
  OUT_MEET: 1018,
  OUT_MONITER: 1019,
  PLAY_DTMF: 1020,
  REGIST_AGC_INFO: 1021,
  ROB_CALL: 1022,
  SET_BUSY: 1023,
  STOP_CALL: 1024,
  TO_CHECK_IN: 1025,
  TO_CHECK_OUT: 1026,
  TRANS_SEAT: 1027,
  UNHOLDER_USER: 1028,
  RECOVER: 1029,
  WHISPER: 1030,
  OUT_WHISPER: 1031,
  TO_SET_BUSY: 1032,
  AUTH_VERIFY: 1100,
  HEART_BEAT: 1101,
  GET_AGENT_STATUS: 1102,
  AGENT_STATUS: 1103,
  GET_AGENT_MESSAGE: 1112,
  CALL_INFO: 1104,
  RING_3: 1108,
  ANSWER_3: 1109,
  HUNGUP_3: 1110,
  RING: 1105,
  ANSWER: 1106,
  HUNGUP: 1107,
  BE_CHECK_OUT: 1111,
  GET_CALL_EVENT_STATUS: 1113,
  GET_STATE_EVENT_STATUS: 1115,
  GET_AGENT_LIST: 2001,
  GET_ENT_QUE_INFO: 2002,
  ALL_ERROR: 9999
}

/**
 * 更新按钮
 * @param {{id: string, title: string, icon: string, titleUpdate: boolean}} options 
 */
const updateBtn = options => {
  const { id, title, icon, titleUpdate } = options
  const el = document.getElementById(id)
  const img = el.querySelector('img')
  const span = el.querySelector('span')

  img.title = title
  img.src = `./images/${icon}.png`

  if (titleUpdate === undefined || titleUpdate) {
    span.textContent = title
  }
}

/**
 * 判断按钮是否启用
 * @param {HTMLLIElement & {fun_value: string}} btn
 */
const btnIsEnable = btn => !!btn && (btn.fun_value || 0) < 10

/**
 * @param {string} id 按钮id
 * @param {0|1|2|3} subFunc 
 * @param {0|1} enable 0可用 1不可用
 */
const setBtnStyle = (id, subFunc, enable) => {
  subFunc = +subFunc
  /** @type {HTMLElement & {fun_value: string}} */
  const btn = document.getElementById(id)

  if (btn) {
    if (enable === 1) {
      btn.fun_value = subFunc + 10
      btn.style.cursor = 'not-allowed'
    } else {
      btn.fun_value = subFunc
      btn.style.cursor = 'pointer'
    }

    switch (id) {
      case 'btn_onoff_line':
        if (subFunc === 0) {
          updateBtn({ id, title: '上线', icon: `btn_online_${enable}` })
        } else {
          updateBtn({ id, title: '下线', icon: `btn_offline_${enable}` })
        }
        break
      case 'btn_ready_busy':
        updateBtn({ id, title: '状态', icon: `btn_state_${enable}`, titleUpdate: false })
        break
      case 'btn_callout':
        if (subFunc === 0) {
          updateBtn({ id, title: '呼出', icon: `btn_callout_${enable}` })
        } else {
          updateBtn({ id, title: '挂机', icon: `btn_hungup_${enable}` })
        }
        break
      case 'btn_hold_unhold':
        if (subFunc === 0) {
          updateBtn({ id, title: '保持', icon: `btn_hold_${enable}` })
        } else {
          updateBtn({ id, title: '恢复', icon: `btn_unhold_${enable}` })
        }
        break
      case 'btn_toivr':
        updateBtn({ id, title: '转满意度', icon: `btn_toivr_${enable}` })
        break
      case 'btn_login':
        if (subFunc === 0) {
          updateBtn({ id, title: '签入', icon: `btn_login_${enable}` })
        } else {
          updateBtn({ id, title: '签出', icon: `btn_logout_${enable}` })
        }
        break
      case 'btn_consult':
        if (subFunc === 0) {
          updateBtn({ id, title: '咨询', icon: `btn_consult_${enable}` })
        } else if (subFunc === 1) {
          updateBtn({ id, title: '结束咨询', icon: `btn_stop_consult_agc_${enable}` })
        } else {
          updateBtn({ id, title: '结束咨询', icon: `btn_stop_consult_out_${enable}` })
        }
        break
      case 'btn_confence': // 0=内三方，1=开始外三，2=结束内三方，3=结束外三	
        if ([0, 1].includes(subFunc)) {
          updateBtn({ id, title: '三方通话', icon: `btn_confence_${enable}` })
        } else {
          updateBtn({ id, title: '接回', icon: `btn_stop_confence_${enable}` })
        }
        break
      case 'btn_transfer':
        if (subFunc === 0) {
          updateBtn({ id, title: '转坐席', icon: `btn_trans_agc_${enable}` })
        } else {
          updateBtn({ id, title: '转移外线', icon: `btn_trans_out_${enable}` })
        }
        break
      case 'btn_monitor':
        updateBtn({ id, title: '监听', icon: `btn_monitor_${enable}` })
        break
      case 'btn_stop_monitor':
        updateBtn({ id, title: '结束监听', icon: `btn_stop_monitor_${enable}` })
        break
      case 'btn_force_join':
        if (subFunc === 0) {
          updateBtn({ id, title: '插话', icon: `btn_join_${enable}` })
        } else {
          updateBtn({ id, title: '退出插话', icon: `btn_stop_join_${enable}` })
        }
        break
      case 'btn_whisper':
        if (subFunc === 0) {
          updateBtn({ id, title: '耳语', icon: `btn_whisper_${enable}` })
        } else {
          updateBtn({ id, title: '退出耳语', icon: `btn_stop_whisper_${enable}` })
        }
        break
      case 'btn_tokeover':
        if (subFunc === 0) {
          updateBtn({ id, title: '强接', icon: `btn_takeover_${enable}` })
        } else {
          updateBtn({ id, title: '挂机', icon: `btn_hungup_${enable}` })
        }
        break
      case 'btn_force_hungup':
        updateBtn({ id, title: '强拆', icon: `btn_force_hungup_${enable}` })
        break
      case 'btn_monqueue':
        updateBtn({ id, title: '队列', icon: `btn_monqueue_${enable}` })
        break
      case 'btn_force_login':
        updateBtn({ id, title: '代签入', icon: `btn_force_login_${enable}`, titleUpdate: false })
        break
      case 'btn_force_logout':
        updateBtn({ id, title: '代签出', icon: `btn_force_logout_${enable}`, titleUpdate: false })
        break
    }
  }
}

/**
 * @param {string} id
 * @param {0|1} enable
 */
const setBtnEnable = (id, enable) => {
  const btn = document.getElementById(id)
  btn && setBtnStyle(id, btn.fun_value, enable)
}

var FlashAgentX = (() => {
  return {
    /** @type {WebSocket} */
    socket: null,
    isConnected: false,
    whenErrorNeedReconnect: true,
    reconTimeout: null,
    isCallIngNum: '',
    agentRole: '0',
    logined: false,
    tryopen: false,
    wsReadyState: WebSocket.CONNECTING,

    g_WSUrl: '',
    g_OptId: '',
    g_extNum: '',
    g_entId: '',
    g_entpwd: '',
    g_agentPwd: '',
    g_willdoSendbuff: '',
    /** @type {GCallEvent} */
    g_callEvent: null,
    g_TokenId: '',
    outsecond: 180,
    presrvicecode: 0,
    state: -1,

    heartCheck: {
      tryPing: 0,
      retimeout: 300,
      timeout: 8000,
      timeoutObj: null,
      serverTimeoutObj: null,
      reset() {
        const _self = this
        clearTimeout(_self.timeoutObj)
        clearTimeout(_self.serverTimeoutObj)
        _self.timeoutObj = _self.serverTimeoutObj = null
        return _self
      },
      start() {
        const _self = this
        const agentX = FlashAgentX
        _self.timeoutObj = setTimeout(() => {
          const ws = agentX.socket
          if (ws && ws.readyState == WebSocket.OPEN) {
            if (_self.tryPing >= 5 && !(_self.tryPing % 5)) {
              agentX.connect()
              agentX.reconnect()
            } else {
              agentX.doSend('ping')
            }
            _self.tryPing++
          }
          _self.serverTimeoutObj = setTimeout(() => {
            if (ws && ws.readyState === WebSocket.CLOSED) {
              ws.close()
            }
          }, _self.retimeout)
        }, _self.timeout)
      }
    },

    connect() {
      const _self = this
      timeLog(`connect: ${_self.g_WSUrl}`)
      const ws = new WebSocket(_self.g_WSUrl)
      ws.onopen = _self.doOpen.bind(_self)
      ws.onclose = _self.doClose.bind(_self)
      ws.onmessage = _self.doMessage.bind(_self)
      ws.onerror = e => timeLog(`socect error: ${e.message || '套接字连接失败'}`, 'error')
      _self.socket = ws
    },

    autoConnect() {
      this.tryopen && this.connect()
    },

    reconnect() {
      if (this.whenErrorNeedReconnect && !this.isConnected) {
        this.reconTimeout = setTimeout(() => {
          timeLog('FlashAgentX.reconnect', 'info')
          this.tryopen = true
        }, 8000)
      }
    },

    /**
     * @param {number} trCode
     * @param {any} trBody
     */
    packHost(trCode, trBody) {
      if (!this.g_OptId) {
        timeLog(`pack error: ${trCode} flashagentx not init!`, 'info')
        return ''
      }
      return JSON.stringify({
        head: {
          hostcode: trCode + '',
          entid: this.g_entId,
          optid: this.g_OptId,
          tokenid: this.g_TokenId
        },
        body: trBody
      })
    },

    /**
     * @param {CloseEvent} e
     */
    doClose(e) {
      const _self = this
      const { code, reason } = e
      timeLog(`onclose: ${code} reason: ${reason}`)
      _self.wsReadyState = 0
      if (code === 1004 && reason !== 'CloseNoActiveClient') {
        // 如果是重复登录 不需要尝试重连
        _self.whenErrorNeedReconnect = false
        callStateHelper.updateCallState(callStateHelper.LOGOUT)
        if (reason !== 'checkout') {
          alert(reason)
        }
      }
      _self.isConnected = false
      _self.reconnect()
    },

    doOpen() {
      const _self = this
      _self.isConnected = true
      _self.tryopen = false
      _self.wsReadyState = WebSocket.OPEN
      if (_self.heartCheck.tryPing >= 5) {
        _self.doSend('ping')
      }
      clearTimeout(_self.reconTimeout)
      _self.reconTimeout = null
      _self.heartCheck.reset().start()
      if (_self.g_willdoSendbuff) {
        _self.doSend(_self.g_willdoSendbuff)
        _self.g_willdoSendbuff = ''
      }
    },

    doSend(buffer) {
      if (!buffer) return
      const _self = this
      if (_self.socket.readyState !== WebSocket.OPEN) {
        _self.g_willdoSendbuff = buffer
        _self.whenErrorNeedReconnect = true
        _self.isConnected = false
        _self.connect()
        _self.reconnect()
        return
      }
      _self.heartCheck.reset().start()
      if (_self.socket && _self.socket.readyState === WebSocket.OPEN) {
        _self.socket.send(buffer)
      } else {
        _self.g_willdoSendbuff = buffer
        _self.socket.close()
        _self.tryopen = true
      }
    },

    /**
     * @param {number} state 
     * @param {boolean} helperStateIsUpdate
     */
    updateState(state, helperStateIsUpdate = true) {
      this.state = state
      helperStateIsUpdate && callStateHelper.updateCallState(state)
    },

    canCall (telno) {
      if (this.isCallIngNum === telno) {
        return alert(`Calling ${telno}, ，稍后再试!`)
      }
      const btnReadyBusy = document.getElementById('btn_ready_busy')
      if (btnReadyBusy && (+btnReadyBusy.fun_value !== 0)) {
        return alert('请在空闲状态下外呼！')
      }
      return true
    },

    /**
     * @template {keyof SendActions} T
     * @param {T} action
     * @param {SendActions[T]} params
     */
    sendAction(action, params) {
      const _self = this
      const _send = (code, body) => _self.doSend(_self.packHost(code, body || {}))

      switch (action) {
        case 'checkin':
          _self.logined = true
          if (!_self.tryopen && _self.wsReadyState === WebSocket.CONNECTING) {
            _self.tryopen = true
            _self.autoConnect()
          }
          return _send(svcCode.CHECK_IN, {
            hotline: '',
            pass: _self.g_agentPwd,
            station: _self.g_extNum,
            entpwd: _self.g_entpwd,
            stationtype: 0,
            checkintype: 0,
            outsecond: _self.outsecond
          })
        case 'checkin2':
          if (!_self.tryopen && _self.wsReadyState === WebSocket.CONNECTING) {
            _self.tryopen = true
            _self.autoConnect()
          }
          return _send(svcCode.CHECK_IN_2, {
            hotline: "",
            pass: _self.g_agentPwd,
            station: _self.g_extNum,
            entpwd: _self.g_entpwd,
            stationtype: 0,
            checkintype: 0,
            outsecond: _self.outsecond
          })
        case 'online':
          return _send(svcCode.ONLINE)
        case 'offline':
          return _send(svcCode.OFFLINE)
        case 'checkout':
          _self.logined = false
          return _send(svcCode.CHECK_OUT)
        case 'obtainaenglist':
          return _send(svcCode.GET_AGENT_LIST, params)
        case 'obtainqueinfo':
          return _send(svcCode.GET_ENT_QUE_INFO, params)
        case 'calluser':
          return _send(svcCode.CALL_USER, params)
        case 'dropcall':
          return _send(svcCode.DROP_USER)
        case 'makecall':
          const telno = params.telno.replace(/(^\s*)|(\s*$)/g, '')
          if (_self.canCall(telno)) {
            _self.isCallIngNum = telno
            setTimeout(() => {
              _self.isCallIngNum = ''
            }, 3000)
            return _send(svcCode.CALL_USER, { telno, exinfo: params.exinfo || '' })
          }
        case 'consult':
          return _send(svcCode.CALL_TO_USER, params)
        case 'freeseat':
          return _send(svcCode.FREE_SEAT)
        case 'droptouser':
          return _send(svcCode.DROP_TO_USER)
        case 'setSeatState':
          return _send(svcCode.SET_BUSY, params)
        case 'holduser':
          return _send(svcCode.HOLDER_USER)
        case 'unholduser':
          return _send(svcCode.UNHOLDER_USER)
        case 'transseat':
        case 'transcall':
          return _send(svcCode.TRANS_SEAT)
        case 'gotoivr':
          if (_self.state === callStateHelper.TALKING) {
            _send(svcCode.GO_TO_FLOW, {
              flowno: `${params.flowtype}:${params.rootactid};${params.actid};${params.extinfo}`
            })
          } else {
            _self.sendAction('dropcall')
          }
          return
        case 'inmeet':
          return _send(svcCode.INMEET)
        case 'outmeet':
          return _send(svcCode.OUT_MEET)
        case 'callseat':
          return _send(svcCode.CALL_SEAT, params)
        case 'callseatEx':
          return _send(svcCode.CALL_SEAT, { toseatno: `${params.toseatno};${params.extinfo}` })
        case 'transqueue':
          return _send(svcCode.CALL_SEAT, { toseatno: `q:${params.queueid};${params.extinfo}` })
        case 'monitor':
          monedagentno = params.toseatno
          return _send(svcCode.MONITER, params)
        case 'outmoniter':
          return _send(svcCode.OUT_MONITER, params)
        case 'insert':
          return _send(svcCode.INSERT)
        case 'outinsert':
          return _send(svcCode.OUT_INSERT)
        case 'whisper':
          return _send(svcCode.WHISPER)
        case 'outwhisper':
          return _send(svcCode.OUT_WHISPER)
        case 'robcall':
          return _send(svcCode.ROB_CALL)
        case 'stopcall':
          return _send(svcCode.STOP_CALL)
        case 'tocheckout':
          return _send(svcCode.TO_CHECK_OUT, params)
        case 'tosetbusy':
          return _send(svcCode.TO_SET_BUSY, params)
        case 'getAgcListState':
          agentgetflag = '2'
          return _send(svcCode.GET_AGENT_LIST, params)
        case 'getEntQueueList':
          entgetflag = '2'
          return _send(svcCode.GET_ENT_QUE_INFO, params)
        default:
          return '无效的动作！'
      }
    },

    /**
     * @param {{ivrData: any, status: any, main: any, sub: any}} data
     */
    transflow(data) {
      const acdInfo = { entId: 0, optId: '', qId: 0 }
      let extinfo = ''
      const _self = this
      if (_self.g_callEvent) {
        extinfo = [_self.g_callEvent.callid, acdInfo.entId, acdInfo.qId, acdInfo.optId].join(',')
      }
      if (data.ivrData) {
        extinfo += `,${data.ivrData}`
      }
      _self.sendAction('gotoivr', {
        flowtype: data.status,
        rootactid: data.main,
        actid: data.sub,
        extinfo
      })
      timeLog("FlashAgentX.gotoivr: "+ extinfo)
    },

    connectHandler() {
      var num = 1
      const tryCount = 6
      const _self = this
      ;(function fn() {
        if (_self.isConnected) {
          sendEventToParentWindow('socketConnected')
        } else if (num < tryCount) {
          console.log('第' + (num++) + '次尝试...')
          setTimeout(fn, 1000)
        }
      })()
    },

    loadConfig(entId, entPwd, agentId, agentPwd, extNum, masterUri) {
      const _self = this
      _self.g_TokenId = genUuid();
      _self.g_OptId = agentId;
      _self.g_extNum = extNum;
      _self.g_entId = entId;
      _self.g_entpwd = entPwd;
      _self.g_agentPwd = agentPwd;
      _self.g_WSUrl = masterUri + _self.g_TokenId + "/" + _self.g_OptId;
      if (!_self.isConnected) {
        timeLog("FlashAgentX.loadConfig", 'info');
        _self.tryopen = true;
        _self.autoConnect();
        _self.connectHandler()
      }
      timeLog(_self.g_WSUrl, 'info');
    },

    onLogin(extNum) {
      // 坐席登录成功，开始注册sip phone
      timeLog("onlogin:" + extNum)
      // 系统登录后会自动示忙，避免人员未就绪立即分配电话进来， 预留 x 秒，自动实现， 具体是否采用由开发人员斟酌
      setTimeout(() => this.sendAction('setSeatState', { type: 0 }), 2000)
      // 处理登录后的界面初始化
      callStateHelper.updateCallState(callStateHelper.LOGIN)
    },

    /**
     * @param {MessageEvent<string>} msgEvent
     */
    doMessage(msgEvent) {
      const _self = this
      _self.heartCheck.tryPing = 0
      _self.wsReadyState = WebSocket.OPEN
      const { data } = msgEvent
      if (data.includes('pong')) {
        const pvar = data.split('#&')
        if (pvar.length > 1) {
          if (_self.presrvicecode > 0 && _self.presrvicecode !== +pvar[1]) {
            _self.presrvicecode = +pvar[1]
            switch (_self.presrvicecode) {
              case svcCode.RING:
                _self.updateState(callStateHelper.RING)
                break
              case svcCode.RING_3:
                _self.updateState(callStateHelper.RING_3)
                break
              case svcCode.ANSWER:
                _self.updateState(callStateHelper.TALKING)
                break
              case svcCode.ANSWER_3:
                _self.updateState(callStateHelper.TALKING, false)
                break
              case svcCode.HUNGUP:
                _self.updateState(callStateHelper.LOGIN)
                break
              case svcCode.HUNGUP_3:
                _self.updateState(callStateHelper.LOGIN, false)
                break
              case svcCode.CHECK_IN:
              case svcCode.CHECK_IN_2:
                _self.logined = true
                _self.onLogin(_self.g_OptId)
                break
              case svcCode.CHECK_OUT:
              case svcCode.BE_CHECK_OUT:
                _self.logined = false
                _self.updateState(callStateHelper.LOGOUT)
                break
              case svcCode.FREE_SEAT:
              case svcCode.DROP_TO_USER:
                _self.sendAction('unholduser')
                break
              case svcCode.HOLDER_USER:
                setBtnStyle('btn_hold_unhold', 1, 0)
                break
              case svcCode.UNHOLDER_USER:
                setBtnStyle('btn_hold_unhold', 0, 0)
                break
              case svcCode.INMEET:
              case svcCode.MONITER:
                _self.updateState(callStateHelper.CONFERENCE)
                break
            }
          }
          if (pvar.length > 2 && pvar[2] === 'ACDGATE') {
            _self.doSend('ping')
          }
        } else if (_self.logined) {
          _self.logined = false
          _self.updateState(callStateHelper.LOGOUT)
          alert('网络抖动，请重新签入！')
        }
        return
      }
      const _data = JSON.parse(data)
      let { direct, caller, called, usid, queueid, recfile, dataccid } = _data.body || {}
      dataccid = decodeURIComponent(decodeURIComponent(dataccid))
      _self.presrvicecode = +_data.head.servicecode
      switch (_self.presrvicecode) {
        case svcCode.AGENT_STATUS:
          if (![null, '', 'undefined'].includes(_data.body.agentrole)) {
            _self.agentRole = _data.body.agentrole
          }
          callStateHelper.updateBtnsStyle(_data.body)
          timeLog(`svcCode.AGENT_STATUS ${_data.body.statetime} client:${Date.now()} server:${_data.body.hostmillis}`)
          timeLog(`svcCode.AGENT_STATUS  与主机时差ms：${Date.now() - _data.body.hostmillis}`)
          return
        case svcCode.GET_STATE_EVENT_STATUS:
          //恢复状态 事件
          timeLog(`svcCode.GET_STATE_EVENT_STATUS: client: ${Date.now()} server: ${_data.body.state.hostmillis}`)
          _self.g_callEvent = _data.body.evt
          _self.g_callEvent.offts = Date.now() - _self.g_callEvent.hostmillis
          callStateHelper.updateBtnsStyle(_data.body.state)
          if (+_self.g_callEvent.callstate === 1) OnRingTime(_self.g_callEvent)
          return
        case svcCode.AUTH_VERIFY:
          //只触发消息，不改变状态计时
          _self.g_callEvent = _data.body
          sendEventToParentWindow('OnAuthSuccess', direct, direct == '1' ? caller : called, usid, queueid, dataccid)
          return;
        case svcCode.RING:
          _self.g_callEvent = _data.body
          _self.g_callEvent.offts = Date.now() - _self.g_callEvent.hostmillis
          OnRingTime(_data.body)
    
          _self.updateState(callStateHelper.RING)
          sendEventToParentWindow('OnCalling', direct, direct == '1' ? caller : called, usid, queueid, dataccid)
          return;
        case svcCode.RING_3:
          timeLog(`svcCode.RING_3 ${_data.body.called3}`)
          _self.g_callEvent = _data.body
          _self.g_callEvent.offts = Date.now() - _self.g_callEvent.hostmillis
          _self.updateState(callStateHelper.RING_3)
          return
        case svcCode.ANSWER:
          _self.g_callEvent = _data.body
          _self.g_callEvent.offts = Date.now() - _self.g_callEvent.hostmillis
          OnAnswer(_data.body)
          _self.updateState(callStateHelper.TALKING)
          sendEventToParentWindow('OnCallConnect', direct, direct == '1' ? caller : called, usid, queueid, recfile, dataccid)
          return
        case svcCode.ANSWER_3:
          _self.g_callEvent = _data.body
          _self.g_callEvent.offts = Date.now() - _self.g_callEvent.hostmillis
          OnAnswer3(_data.body)
          _self.updateState(callStateHelper.TALKING, false)
          timeLog("svcCode.ANSWER_3  " + _data.body.called3)
          return;
        case svcCode.HUNGUP: // _direct： 坐席挂机1，客户挂机0
          timeLog(`svcCode.HUNGUP ${_data.body}`)
          timeLog(`onhungupTime：${_data.body.endtime}`)
          _self.updateState(callStateHelper.LOGIN)
          sendEventToParentWindow('OnCallHangup', direct)
          _self.g_callEvent = null
          return
        case svcCode.HUNGUP_3:
          _self.g_callEvent = _data.body
          _self.g_callEvent.offts = Date.now() - _self.g_callEvent.hostmillis
          _self.updateState(callStateHelper.LOGIN, false)
          timeLog(`svcCode.HUNGUP_3 ${_data.body.called3}`)
        case svcCode.CHECK_IN:
        case svcCode.CHECK_IN_2:
          if (_data.head.errcode == 0) {
            g_logined = 1;
            if ("110" == _data.body.tokenid) {
              return
            }
            _self.g_TokenId = _data.body.tokenid
            g_TokenId = _data.body.tokenid
            _self.logined = true
            return _self.onLogin(_self.g_OptId)
          } else {
            if (_data.head.errcode == -19200) {
              if (confirm("您的工号已在其他地方签入，要继续签入吗？")) {
                _self.sendAction('checkin2')
              }
            } else {
              timeLog("登录失败:" + _data.head.errmsg, 'info')
            }
            return
          }
        //签出，忽略结果
        case svcCode.CHECK_OUT:
          _self.updateState(callStateHelper.LOGOUT)
          _self.logined = false
          return
        case svcCode.BE_CHECK_OUT:
          _self.updateState(callStateHelper.LOGOUT)
          _self.logined = false
          return
        case svcCode.CALL_USER:
          sendEventToParentWindow('OnCallReturn', _data.head.errcode)
          timeLog(`呼出失败，errcode=${_data.head.errcode} errmsg=${_data.head.errmsg}`, 'info')
          if (_data.head.errcode == "-19200") {
            const msg = '对不起，系统正在呼出 ，请稍后再试！'
            alert(msg)
            timeLog(msg, 'info')
            return
          }
          if (_data.head.errcode != "0") {
            const msg = `对不起,呼出失败[${_data.head.errmsg}]`
            alert(msg)
            timeLog(msg, 'info')
          }
          return
        case svcCode.DROP_TO_USER:
          FlashAgentX.sendAction('unholduser')
          break //当退出咨询后，向acd发起恢复客户电话请求
        case svcCode.FREE_SEAT:
          FlashAgentX.sendAction('unholduser')
          break
        case svcCode.HOLDER_USER:
          setBtnStyle("btn_hold_unhold", 1, 0)
          break
        case svcCode.UNHOLDER_USER:
          setBtnStyle("btn_hold_unhold", 0, 0)
          break
        case svcCode.INMEET:
        case svcCode.MONITER:
          FlashAgentX.updateState(callStateHelper.CONFERENCE)
          break
        case svcCode.GET_AGENT_LIST:
          agentlists = _data.body
          agentgetflag = "1"
          break
        case svcCode.GET_ENT_QUE_INFO:
          entques = _data.body
          entgetflag = "1"
          break
      }
    }
  }
})()

setInterval(() => FlashAgentX.autoConnect(), 10000)
